
#include "world.h"

#include "coid/comm/dir.h"
//#include "../rsp/s_room.h"


//RSPC_CLASS(m,man);
//RSPC_CLASS(b,bomb);

////////////////////////////////////////////////////////////////////////////////
int TESTING_MODE = 2;

collisionbody   TILE::_tilebody(    coord(0x8000),  coord(0x3800),  collisionbody::fSQUARE );
collisionbody   man::_stcoll(       coord(0x5200),  coord(0),       collisionbody::fNOSELFCOLLIDE );
collisionbody   bomb::_stcoll(      coord(0x8000),  coord(0xc000),  0 );


dynarray<World::scheme>     World::_schemes;

uint    World::_gamesave = UMAX;
charstr World::_saveidpath;

////////////////////////////////////////////////////////////////////////////////
uint moving::SPEED_BASE = 0;
uint moving::SPEED_DELTA = 0;
uint moving::SPEED_VOLANT = 0;
uint moving::SPEED_CRAWL = 0;
uint moving::SPEED_BOMB = 0;
uint moving::SPEED_PUNCH = 0;
uint moving::SPEED_PUNCH_MAN = 0;
uint moving::SPEED_AROUND = 0;
uint moving::GRAV_DECCEL = 0;


////////////////////////////////////////////////////////////////////////////////
ushort World::get_version()
{
    ///////////// BM VERSION //////////////
    return 12;
}

////////////////////////////////////////////////////////////////////////////////
World::World() : _rnd(1)
{
    DASSERT( MEN <= 16 );    //tile cannot hold more

    //x_winner = 0xff;
    _nroundstowin = 7;

    if(TESTING_MODE<=1)
    {
        _minplstart = 1;
        _minplkeep = 1;
    }
    else
    {
        _minplstart = TESTING_MODE;
        _minplkeep = 2;
    }

    _maxplayers = 8;

    gs.state = GS_BOOTING;
    gs.counter = 0;

    _prob_rnd = 1;

    load_default_pwup_probs();
    load_default_pwup_counts();

    _men.init(MEN);
    { for( uint i=0; i<MEN; ++i ) {
        _men[i]->set_color(i);
        _men[i]->reset(false,_pwupinit.ptr());
    }}

    uint usec = _Timer.get_period_usec();
    moving::init_constants(usec);

    _detcount = MSEC_TO_TICKS(BOMB_DETCOUNTER);
    _detcount_burn = MSEC_TO_TICKS(BOMB_DETCOUNTER_BURN);

    _round_msec = 2 * 60 * 1000;   //2 min default

    if( _minplstart == 1 )
        _round_msec *= 2;


    if( _gamesave == UMAX )
        load_gamesave_id();

    //game_begin();
    //game_end(1);
}

////////////////////////////////////////////////////////////////////////////////
void World::load_default_pwup_probs()
{
    _prob.need_new(POWERUP::NUM_PWUP);

    _prob[POWERUP::NONE]        = 10;

    _prob[POWERUP::BOMB]        = 30;
    _prob[POWERUP::FLAME]       = 20;
    _prob[POWERUP::GOLDFLAME]   = 2;
    _prob[POWERUP::KICK]        = 10;
    _prob[POWERUP::GRAB]        = 6;
    _prob[POWERUP::PUNCH]       = 5;
    _prob[POWERUP::JELLY]       = 5;
    _prob[POWERUP::TRIGGER]     = 1;
    _prob[POWERUP::SKATE]       = 20;
    _prob[POWERUP::SPOOGE]      = 6;
    _prob[POWERUP::BRAKE]       = 3;
    
    _prob[POWERUP::CRAWL]       = 3;
    _prob[POWERUP::VOLANT]      = 3;
    _prob[POWERUP::NOBOMBS]     = 3;
    _prob[POWERUP::EJECT]       = 3;
    _prob[POWERUP::FASTDETONATE]= 2;
    _prob[POWERUP::REVERSE]     = 2;
    _prob[POWERUP::SHORTFLAME]  = 3;
    _prob[POWERUP::THROWFLAME]  = 1;
    _prob[POWERUP::THROWSKATE]  = 1;
    _prob[POWERUP::THROWBOMB]   = 1;
    _prob[POWERUP::THROWSPECIAL]= 1;
    _prob[POWERUP::COMBINED]    = 3;

    compute_prob_total();
}

////////////////////////////////////////////////////////////////////////////////
void World::compute_prob_total()
{
    _prob_tot = 0;
    for( uint h=0; h<POWERUP::NUM_PWUP; ++h )
        _prob_tot += _prob[h];

    _prob_rnd = _Timer.time();
}
////////////////////////////////////////////////////////////////////////////////
void World::load_default_pwup_counts()
{
    _pwupinit.need_newc(POWERUP::NUM_PWUP);

    _pwupinit[POWERUP::BOMB]    = 1;
    _pwupinit[POWERUP::FLAME]   = 2;
    _pwupinit[POWERUP::SKATE]   = 1;
}

////////////////////////////////////////////////////////////////////////////////
void World::generate_powerup( TILE* t, uint x, uint y )
{
    uint prnd = rsp_controller::current->get_current_stateid() + ((x+1)<<3) + ((y+1)<<7);

    uchar pwup = assign_powerup(prnd);
    if( pwup != 0 )
        t->add_powerup(pwup);
}

////////////////////////////////////////////////////////////////////////////////
void World::redistribute_powerups()
{
    uint n = _redistpow.size();

    //uint prnd = (rsp_controller::current->get_current_stateid() + 1) * rnd_int::get_multiplier();
    //_rnd.seed(prnd);

    for( uint i=0; n>0 && i<XTILES*YTILES; ++i )
    {
        uint r = _rnd.rand();
        uint x = r % XTILES;
        uint y = (r>>16) % YTILES;

        TILE* t = get_tile(x,y);
        if( t->type != TILE::EMPTY )  continue;
        if( t->has_object() || t->is_burning() )  continue;

        --n;
        t->add_powerup(_redistpow[n]);
    }

    _redistpow.reset();
}

////////////////////////////////////////////////////////////////////////////////
uint World::load_schemes()
{
    directory d;

#ifdef SYSTYPE_WIN32
    const char* path = "data\\schemes\\";
#else
    const char* path = "data/schemes/";
#endif

    if( 0 != d.open( path, "*.sch" ) )  return 0;

    uint n=0;
    while( d.next() )
    {
        if( !load_scheme( d.get_last_full_path(), _schemes.need(n+1)[n] ) )
            _schemes.need(n);
        else
            ++n;
    }

    LOG(-1) { *TLOG << "loaded " << _schemes.size() << " scheme files" << BINSTREAM_FLUSH; }

    return n;
}

////////////////////////////////////////////////////////////////////////////////
bool World::load_scheme( const token& file, scheme& sch )
{
    fileiostream bif(file);
    if(!bif.is_open())  return false;

    fmtstreamcxx tin(bif);

    try
    {
        tin >> sch._name;

        dynarray<uchar>::binstream_container bc1 = sch._prob.get_container(POWERUP::NUM_PWUP);
        tin.xread_array(bc1);

        dynarray<uchar>::binstream_container bc2 = sch._pwupinit.get_container(POWERUP::NUM_PWUP);
        tin.xread_array(bc2);

        //tin.read_compound_array( sch._prob.need_new(POWERUP::NUM_PWUP), POWERUP::NUM_PWUP );
        //tin.read_compound_array( sch._pwupinit.need_new(POWERUP::NUM_PWUP), POWERUP::NUM_PWUP );

        TILE* pt = sch._atile.need_new(XTILES*YTILES);
        int lin = YTILES-1;
        pt += lin*XTILES;
        for(;;)
        {
            charstr line;
            tin >> line;

            if( line.len() != XTILES+1 )
                return false;

            if( line[0] != line[XTILES] )
                return false;

            for( uchar i=0; i<XTILES; ++i )
            {
                switch( line[i] ) {
                case '#':   pt[i].set_type( TILE::HARD, 0 ); break;
                case '*':   pt[i].set_type( TILE::SOFT, 0 ); break;
                case '+':   pt[i].set_type( TILE::SOFT, TILE::fNOSTART ); break;
                case ' ':   pt[i].set_type( TILE::EMPTY, TILE::GRASS ); break;
                case '.':   pt[i].set_type( TILE::EMPTY, TILE::GRASS|TILE::fNOSTART ); break;
                }
            }
            pt -= XTILES;

            --lin;
            if( lin < 0 )
                return true;
        }
    }
    catch(...)
    {
    }

    return false;
}

////////////////////////////////////////////////////////////////////////////////
void World::get_scheme()
{
    _atile = _schemes[0]._atile;
    _prob = _schemes[0]._prob;
    _pwupinit = _schemes[0]._pwupinit;

    compute_prob_total();

    touch();
}

////////////////////////////////////////////////////////////////////////////////
rsp_tile* World::frame()
{
    uint si = rsp_controller::current->get_current_stateid();
    _rnd.seed( si * 19 );
    process_gamestate();

    map();

    {for( uint mi=0; mi<MEN; ++mi ) {
        man& m = *_men[mi];
        uint cmd = m.pop_cmdreq();

        if( m.in_world() )
        {
            while(cmd)
            {
                uint c = cmd >> 24;
                if( c == man::fBTN_PRESS + man::BTN_ACT_A )
                    set_bomb(m);
                else if( c == man::BTN_ACT_A )
                    throw_bomb(m);
                else if( c == man::fBTN_PRESS + man::BTN_ACT_B )
                {
                    if( !punch_bomb(m)  &&  !m.fire_trigger() )
                        stop_bombs(m);
                }

                cmd <<= 8;
            }

            if( m.get_powerup(POWERUP::EJECT) )
            {
                throw_bomb(m);
                set_bomb( m, true );
            }

            man_tick(&m);
        }
    }}

    uint menalive=0;
    uint lastalive=UMAX;
    {for( uint m=0; m<MEN; ++m )
    {
        if( _men[m]->in_world() )
            ++menalive, lastalive=m;
        
        const rsp_dynarray<bomb,true>& ba = _men[m]->get_bombs();
        for( uint b=0; b<ba.size(); ++b )
            if( ba[b]->in_world() )  bomb_tick( ba[b] );
    }}

    if( _redistpow.size() > 0 )
        redistribute_powerups();

    uint mba = 1;
    if( TESTING_MODE<=1 )
    {
        //rsp_s_world_controller* rc = get_world_controller();
        //mba = (rc && rc->get_active_players() <= 1)  ?  0 : 1;
        mba = (gs.players_ac <= 1)  ?  0 : 1;
    }

    SERVER_ONLY_BEGIN
        if( menalive <= mba  &&  gs.state == GS_ROUND )
        {
            //round won or draw
            if( menalive && _men[lastalive]->in_world() && !_men[lastalive]->in_fire() )
            {
                if( _nroundstowin > _men[lastalive]->inc_wins() )
                    set_gamestate( GS_ROUND_END, GST_ROUND_END );
                else
                    set_gamestate( GS_GAME_END, GST_GAME_END );
            }
            else
                set_gamestate( GS_ROUND_END_DRAW, GST_ROUND_END );
            mark_active_men();
        }
    SERVER_ONLY_END

    return 0;
}

////////////////////////////////////////////////////////////////////////////////
void World::mark_active_men()
{
    for( uint m=0; m<MEN; ++m )
    {
        if( _men[m]->is_connected() )
            _men[m]->touch();
    }
}

////////////////////////////////////////////////////////////////////////////////
void World::process_gamestate()
{
    if( gs.counter > 0 )
    {
        --gs.counter;

        //printf("\r%i\t%i\t", (gs.counter*_Timer.get_period())/1000, _Timer.time()/1000 );
        
        //here exclude the stuff that needs periodical checking AND timeout
        if( gs.state != GS_ROUND_NOT_ENOUGH_PLAYERS )
            return;
    }

    SERVER_ONLY_BEGIN

    //rsp_s_room_controller* rc = get_room_controller();

    if( gs.state == GS_ROUND )
    {
        //round time expired 
        set_gamestate( GS_ROUND_END_DRAW, GST_ROUND_END );
    }
    else if( gs.state == GS_ROUND_END  ||  gs.state == GS_ROUND_END_DRAW )
    {
        ++_grc._rounds;
        if( gs.state == GS_ROUND_END_DRAW )
            ++_grc._draws;

        enter_gamestate_round_prepare(true);
    }
    else if( gs.state == GS_ROUND_PREPARE )
    {
        set_gamestate( GS_ROUND, _round_msec );
    }
    else if( gs.state == GS_ROUND_NOT_ENOUGH_PLAYERS )
    {
        if( gs.players_ac >= _minplkeep )
            enter_gamestate_round_prepare(true);
        else if( gs.counter == 0 )
            //timeout for the reconnects expired, scratch the whole thing
            set_gamestate( GS_GAME_END_SCRATCH, GST_GAME_END_SCRATCH );
    }
    else if( gs.state == GS_GAME_WAIT )
    {
        if( gs.players_ac >= _minplstart )
            set_gamestate( GS_GAME_START, GST_GAME_START );
    }
    else if( gs.state == GS_GAME_START )
    {
        if( gs.players_ac < _minplstart )
            set_gamestate( GS_GAME_WAIT, 0 );
        else
        {
            game_begin();

            enter_gamestate_round_prepare(false);
        }
    }
    else if( gs.state == GS_BOOTING )
    {
        set_gamestate( GS_GAME_WAIT, 0 );
    }
    else if( gs.state == GS_GAME_END || gs.state == GS_GAME_END_SCRATCH )
    {
        game_end( gs.state == GS_GAME_END_SCRATCH );

        set_gamestate( GS_BOOTING, GST_BETWEEN_GAMES );
        //rc->request_detach_all_connectors();
    }

    SERVER_ONLY_END
}

////////////////////////////////////////////////////////////////////////////////
void World::set_gamestate( int gst, uint timeout )
{
    SERVER_ONLY_BEGIN

    gs.state = gst;
    gs.counter = MSEC_TO_TICKS(timeout);

    gs.touch();
    SERVER_ONLY_END
}

////////////////////////////////////////////////////////////////////////////////
void World::enter_gamestate_round_prepare( bool round )
{
    if( gs.players_ac < _minplkeep )
    {
        set_gamestate( GS_ROUND_NOT_ENOUGH_PLAYERS, GST_ROUND_NOT_ENOUGH_PLAYERS );
        return;
    }

    get_scheme();
    generate( rsp_controller::current->get_current_stateid(), round );

    set_gamestate( GS_ROUND_PREPARE, GST_ROUND_PREPARE );
}

////////////////////////////////////////////////////////////////////////////////
void World::game_begin()
{
    charstr sf = "save";
    directory::mkdir(sf);

    sf << directory::separator() << _gamesave << ".bm?wec";
    opcd e = _grc._fsav.open(sf);
    if(e) {
        LOG(0) { *TLOG << "ERROR: cannot open file for game logging: " << sf << BINSTREAM_FLUSH; }
    }

    ++_gamesave;
    save_gamesave_id();

    _grc._tsav.bind(_grc._fsav);
    _grc._meta.bind_formatting_stream(_grc._tsav);

    _grc.reset();
}

////////////////////////////////////////////////////////////////////////////////
void World::game_end( bool scratched )
{
    if( !_grc._fsav.is_open() )
        return;

    _grc._end.now();

    _grc._meta.stream_out( _gamesave-1,  "game_number" );
    _grc._meta.stream_out( get_version(), "version_bm" );
    _grc._meta.stream_out( Grsp_version, "version_rsp" );
    _grc._meta.stream_out( scratched,    "scratched" );

    _grc._meta.stream_out( _grc._rounds, "rounds" );
    _grc._meta.stream_out( _grc._draws,  "draw_rounds" );

    _grc._meta.stream_out( _grc._begin,  "game_start_time" );
    _grc._meta.stream_out( _grc._end,    "game_end_time" );

    rsp_dynarray<man,true>::container bc = _men.get_container();
    _grc._meta.stream_out_array( bc,     "players" );

    _grc._fsav.close();


    for( uint m=0; m<MEN; ++m )
        _men[m]->reset( false, _pwupinit.ptr() );
}

////////////////////////////////////////////////////////////////////////////////
void World::generate( uint seed, bool round )
{
	if( seed == 0 )
	    seed = 1;
	_rnd.seed(seed);

    //assign player positions
    {for( uint i=0; i<MEN; ++i )
    {
        _men[i]->reset( round, _pwupinit.ptr() );
        if( !_men[i]->is_connected() )  continue;

        uint r = _rnd.rand();
        uint x = r % XTILES;
        uint y = (r>>16) % YTILES;

        _men[i]->set_pos( x, y );
        if( 0 >= try_object( _men[i], true, 0 ) )  { --i; continue; }

        //_men[i]->set_color(i);

        body_enter( _men[i] );
        _men[i]->touch();
    }}
}

////////////////////////////////////////////////////////////////////////////////
void World::flame_implode( uint x, uint y, int len, const uchar* size )
{
    flame_clr( x, y, true, true, 0 );

    uint lenm = len-1;
    uint i,n;

    for( i=1,n=size[0]; i<n; ++i )  flame_clr( x+i, y, true, false, i==lenm ? 1 : 0 );
    for( i=1,n=size[1]; i<n; ++i )  flame_clr( x-i, y, true, false, i==lenm ? -1 : 0  );
    for( i=1,n=size[2]; i<n; ++i )  flame_clr( x, y+i, false, true, i==lenm ? 1 : 0  );
    for( i=1,n=size[3]; i<n; ++i )  flame_clr( x, y-i, false, true, i==lenm ? -1 : 0  );
}


////////////////////////////////////////////////////////////////////////////////
void World::flame_explode( uint x, uint y, uchar plybomb, uchar ignitor, int len, uchar* size )
{
    //exploding
    //center
    flame_set( x, y, plybomb, ignitor, true, true, 0 );

    uint lenx = len > World::XTILES  ?  World::XTILES  :  len;
    uint leny = len > World::YTILES  ?  World::YTILES  :  len;

    uint lenm = len-1;
    uint i;

    for( i=1; i<lenx; ++i )
    {
        int r = flame_set( x+i, y, plybomb, ignitor, true, false, i==lenm ? 1 : 0 );
        if( r == 0 )  ++i;
        if( r <= 0 )  break;
    }
    size[0] = (uchar)i;

    for( i=1; i<lenx; ++i )
    {
        int r = flame_set( x-i, y, plybomb, ignitor, true, false, i==lenm ? -1 : 0 );
        if( r == 0 )  ++i;
        if( r <= 0 )  break;
    }
    size[1] = (uchar)i;

    for( i=1; i<leny; ++i )
    {
        int r = flame_set( x, y+i, plybomb, ignitor, false, true, i==lenm ? 1 : 0 );
        if( r == 0 )  ++i;
        if( r <= 0 )  break;
    }
    size[2] = (uchar)i;

    for( i=1; i<leny; ++i )
    {
        int r = flame_set( x, y-i, plybomb, ignitor, false, true, i==lenm ? -1 : 0 );
        if( r == 0 )  ++i;
        if( r <= 0 )  break;
    }
    size[3] = (uchar)i;
}
